#include "exceptions.hpp"
#include "streamAccess.hpp"
#include "streamUtil.hpp"
#include <iomanip>

namespace hms {


template<typename Derived>
std::ostream& write(
	std::ostream& os,
	const Eigen::DenseBase<Derived>& d
){
	/* setting IOFormat */
	Eigen::IOFormat iof{
		hms::io::writePrecision, /* write precision (default: StreamPrecision) */
		Eigen::DontAlignCols,    /* flags (default: 0) */
		" ",               /* coeffSeparator (default: " ") */
		"",  /* rowSeparator (default: "\n") */
		"",  /* rowPrefix    (default: "") */
		"",  /* rowSuffix    (default: "") */
		"",  /* matPrefix    (default: "") */
		""   /* matSuffix    (default: "") */
	};
	
	/* reshaping matrix into a row vector */
	return os << d.derived().reshaped().transpose().format(iof);
}



template<typename Derived>
std::istream& read(
	std::istream& is,
	const Eigen::EigenBase<Derived>& mConst
){
	/* according to http://eigen.tuxfamily.org/dox-devel/TopicFunctionTakingEigenTypes.html
	 * the only way for a generic implementation for all Eigen types is taking
	 * a const reference and casting the const-ness away */
	Eigen::EigenBase<Derived>& m = const_cast< Eigen::EigenBase<Derived>& >(mConst);
	/* column-major storage, therefore columns have to be outer loop.
	 * the i-loop iterates through rows, i.e. forms a column, and therefore
	 * must be the inner one */
	for ( Index j {0}; j < m.cols(); ++j )
	{
		for ( Index i {0}; i < m.rows(); ++i )
		{
			/* char must be treated differently to avoid non-numeric content to
			 * be converted into a char and then inserted */
			if constexpr ( std::is_same_v<typename Derived::Scalar, char> ){
				int c;
				if ( is >> c ){
					m.derived()(i,j) = c;
					continue;
				}
				/* If extraction failed, the stream is already in a fail-state
				 * and the extraction in the condition below should fail as
				 * well. If extraction succeeded, the clause below is skipped
				 * due to the "continue" instruction.
				 * */
			}
			if ( !(is >> m.derived()(i,j) ) ) {
				/* construct error message */
				std::string str {
					"Could not read matrix coefficient "
					"i=" + std::to_string(i) + ", "
					"j=" + std::to_string(j) + "! "
				};
				if ( is.eof() )
					throw IOStreamError( str + "Reached EOF!" );
				if ( is.fail() ) {
					is.clear();
					str += std::string{ "Expected scalar, got \"" }
						+ unexpectedToken(is)
						+ "\".";
					throw IOStreamError( str );
				}
			}
		}
	}
	return is;
}


template<typename Derived>
DerivedIfDenseBase<Derived> read( std::istream& is ){
	static_assert(
		Derived::RowsAtCompileTime != Eigen::Dynamic &&
		Derived::ColsAtCompileTime != Eigen::Dynamic,
		"This function is for fixed size Eigen::DenseBase types only!"
	);

	Derived d;
	read(is, d);
	return d;
}

template<typename Derived>
DerivedIfDenseBase<Derived> read( std::istream& is, Index size ){
	static_assert(
		(
			Derived::RowsAtCompileTime == Eigen::Dynamic &&
			Derived::ColsAtCompileTime == 1
		) || (
			Derived::RowsAtCompileTime == 1 &&
			Derived::ColsAtCompileTime == Eigen::Dynamic
		),
		"This function is for Eigen::DenseBase vector types only!"
	);

	Derived d;
	if constexpr ( Derived::ColsAtCompileTime == 1 )
		d.resize(size);
	else
		d.resize(1, size);
	
	read(is, d);
	return d;
}

template<typename Derived>
DerivedIfDenseBase<Derived> read(std::istream& is, Index rows, Index cols){
	Derived d (rows, cols);
	read(is, d);
	return d;
}


template<typename Derived>
std::string toString( const Eigen::DenseBase<Derived>& m )
{
	std::stringstream ss;
	
	/* flattening is already defined in hms::operator<<(ostream&, Matrix) */
	write(ss, m);
	
	return ss.str();
}


} // end namespace hms